package com.seadragon.app.ksr.service.impl;

import static com.seadragon.app.ksr.model.KsrConstants.ENTITY_TYPE_BUSINESS;
import static com.seadragon.app.ksr.model.KsrConstants.ENTITY_TYPE_CUSTOMER;
import static com.seadragon.app.ksr.util.FileUtils.createUniqueDirectory;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.seadragon.app.ksr.dao.PictureDao;
import com.seadragon.app.ksr.model.Customer;
import com.seadragon.app.ksr.model.Office;
import com.seadragon.app.ksr.model.Picture;
import com.seadragon.app.ksr.service.CustomerService;
import com.seadragon.app.ksr.service.ImageService;
import com.seadragon.app.ksr.service.OfficeService;

@Service("imageService")
public class ImageServiceImpl implements ImageService {
	private Logger logger = LoggerFactory.getLogger(ImageServiceImpl.class);
	
	private static final String IMAGE_FORMAT_PNG = "png";
	@Value("${docroot}")
	private String docRoot;

	@Value("${base.url}")
	private String baseUrl;

	@Value("${profile.folder}")
	private String profileFolder;

	@Value("${profile.image.large.width}")
	private int profileImageLargeWidth;

	@Value("${profile.image.large.height}")
	private int profileImageLargeHeight;

	@Value("${profile.image.large.fn}")
	private String profileImageLargeFilename;

	@Value("${profile.image.median.width}")
	private int profileImageMedianWidth;

	@Value("${profile.image.median.height}")
	private int profileImageMedianHeight;

	@Value("${profile.image.median.fn}")
	private String profileImageMedianFilename;

	@Value("${profile.image.large.square.width}")
	private int profileImageLargeSquareWidth;

	@Value("${profile.image.large.square.fn}")
	private String profileImageLargeSquareFilename;

	@Value("${profile.image.median.square.width}")
	private int profileImageMedianSquareWidth;

	@Value("${profile.image.median.square.fn}")
	private String profileImageMedianSquareFilename;

	@Value("${profile.image.small.square.width}")
	private int profileImageSmallSquareWidth;

	@Value("${profile.image.small.square.fn}")
	private String profileImageSmallSquareFilename;

	@Value("${profile.image.extra.small.square.width}")
	private int profileImageExtraSmallSquareWidth;

	@Value("${profile.image.extra.small.square.fn}")
	private String profileImageExtraSmallSquareFilename;

	@Value("${profile.image.90.square.width}")
	private int profileImage90SquareWidth;

	@Value("${profile.image.90.square.fn}")
	private String profileImage90SquareFilename;

	@Value("${profile.image.60.square.width}")
	private int profileImage60SquareWidth;

	@Value("${profile.image.60.square.fn}")
	private String profileImage60SquareFilename;

	@Value("${profile.image.30.square.width}")
	private int profileImage30SquareWidth;

	@Value("${profile.image.30.square.fn}")
	private String profileImage30SquareFilename;


	@Autowired
	private CustomerService customerService;
	
	@Autowired
	private OfficeService officeService;

	@Autowired
	private PictureDao pictureDao;
	
	@Override
	public BufferedImage resize(BufferedImage originalImage, int targetImageWidth, int targetImageHeight) {
		BufferedImage resizedImage = new BufferedImage(targetImageWidth, targetImageHeight, originalImage.getType());
		Graphics2D g = resizedImage.createGraphics();
		g.setComposite(AlphaComposite.Src);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.drawImage(originalImage, 0, 0, targetImageWidth, targetImageHeight, null);
		g.dispose();
		return resizedImage;
	}

	@Override
	@Transactional
	public Picture save(byte[] imageBytes, Customer customer) {
		Picture picture = save(imageBytes, true, true, false, true, true, true, false, true, true);
		customer.getPictures().add(picture);
		customerService.update(customer);
		return picture;
	}

	@Override
	@Transactional
	public Picture save(byte[] imageBytes, Office office) {
		Picture picture = save(imageBytes, true, true, true, true, false, false, true, false, false);
		office.getPictures().add(picture);
		officeService.update(office);
		return picture;
	}

	private Picture save(byte[] imageBytes, boolean bl, boolean bm, 
			boolean bls, boolean bms, boolean bss, boolean bxss, boolean b90s, boolean b60s, boolean b30s) {
		InputStream is = new ByteArrayInputStream(imageBytes);
		String photoFolder = createUniqueDirectory(docRoot + profileFolder);
		Picture picture = null;
		try {
			picture = new Picture();
			picture.setPictureFolder(photoFolder);
			picture.setRootPath(profileFolder);
			BufferedImage sourceImage = ImageIO.read(is);
			File dir = new File(docRoot + profileFolder + photoFolder);
			if(!dir.exists()){
				dir.mkdir();
			}
			if(bl){
				int targetWidth = 0;
				int targetHeight = 0;
				if(sourceImage.getWidth()*profileImageLargeHeight/sourceImage.getHeight() > profileImageLargeWidth){
					targetWidth = profileImageLargeWidth;
					targetHeight = (sourceImage.getHeight()*profileImageLargeWidth)/sourceImage.getWidth();
				}else{
					targetWidth = (sourceImage.getWidth()*profileImageLargeHeight)/sourceImage.getHeight();
					targetHeight = profileImageLargeHeight;
				}
				BufferedImage largeImage = resize(sourceImage, targetWidth, targetHeight);
				
				ImageIO.write(largeImage, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImageLargeFilename + "." + IMAGE_FORMAT_PNG));
				picture.setLargeImageFilename(profileImageLargeFilename + "." + IMAGE_FORMAT_PNG);
			}
			if(bm){
				int targetWidth = 0;
				int targetHeight = 0;
				if(sourceImage.getWidth()*profileImageMedianHeight/sourceImage.getHeight() > profileImageMedianWidth){
					targetWidth = profileImageMedianWidth;
					targetHeight = (sourceImage.getHeight()*profileImageMedianWidth)/sourceImage.getWidth();
				}else{
					targetWidth = (sourceImage.getWidth()*profileImageMedianHeight)/sourceImage.getHeight();
					targetHeight = profileImageMedianHeight;
				}
				BufferedImage largeImage = resize(sourceImage, targetWidth, targetHeight);
				ImageIO.write(largeImage, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImageMedianFilename + "." + IMAGE_FORMAT_PNG));
				picture.setMedianImageFilename(profileImageMedianFilename + "." + IMAGE_FORMAT_PNG);
			}
			BufferedImage squareSourceImage = cropImage(sourceImage,
					sourceImage.getWidth() > sourceImage.getHeight() ? sourceImage.getHeight() : sourceImage.getWidth(),
					sourceImage.getWidth() > sourceImage.getHeight() ? sourceImage.getHeight() : sourceImage.getWidth());

			if(bls){
				BufferedImage largeSquareImage = resize(squareSourceImage, profileImageLargeSquareWidth, profileImageLargeSquareWidth);
				ImageIO.write(largeSquareImage, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImageLargeSquareFilename + "." + IMAGE_FORMAT_PNG));
				picture.setLargeSquareImageFilename(profileImageLargeSquareFilename + "." + IMAGE_FORMAT_PNG);
			}
			if(bms){
				BufferedImage medianSquareImage = resize(squareSourceImage, profileImageMedianSquareWidth, profileImageMedianSquareWidth);
				ImageIO.write(medianSquareImage, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImageMedianSquareFilename + "." + IMAGE_FORMAT_PNG));
				picture.setMedianSquareImageFilename(profileImageMedianSquareFilename + "." + IMAGE_FORMAT_PNG);
			}
			if(bss){
				BufferedImage smallSquareImage = resize(squareSourceImage, profileImageSmallSquareWidth, profileImageSmallSquareWidth);
				ImageIO.write(smallSquareImage, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImageSmallSquareFilename + "." + IMAGE_FORMAT_PNG));
				picture.setSmallSquareImageFilename(profileImageSmallSquareFilename + "." + IMAGE_FORMAT_PNG);
			}
			if(bxss){
				BufferedImage extraSmallSquareImage = resize(squareSourceImage, profileImageExtraSmallSquareWidth, profileImageExtraSmallSquareWidth);
				ImageIO.write(extraSmallSquareImage, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImageExtraSmallSquareFilename + "." + IMAGE_FORMAT_PNG));
				picture.setExtraSmallSquareImageFilename(profileImageExtraSmallSquareFilename + "." + IMAGE_FORMAT_PNG);
			}
			if(b90s){
				BufferedImage square90Image = resize(squareSourceImage, profileImage90SquareWidth, profileImage90SquareWidth);
				ImageIO.write(square90Image, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImage90SquareFilename + "." + IMAGE_FORMAT_PNG));
				picture.setSquare90ImageFilename(profileImage90SquareFilename + "." + IMAGE_FORMAT_PNG);
			}
			if(b60s){
				BufferedImage square60Image = resize(squareSourceImage, profileImage60SquareWidth, profileImage60SquareWidth);
				ImageIO.write(square60Image, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImage60SquareFilename + "." + IMAGE_FORMAT_PNG));
				picture.setSquare60ImageFilename(profileImage60SquareFilename + "." + IMAGE_FORMAT_PNG);
			}
			if(b30s){
				BufferedImage square30Image = resize(squareSourceImage, profileImage30SquareWidth, profileImage30SquareWidth);
				ImageIO.write(square30Image, IMAGE_FORMAT_PNG, new File(docRoot + profileFolder + photoFolder + File.separator + profileImage30SquareFilename + "." + IMAGE_FORMAT_PNG));
				picture.setSquare30ImageFilename(profileImage30SquareFilename + "." + IMAGE_FORMAT_PNG);
			}
		} catch (IOException ex) {
			logger.error("Failed to read input stream!", ex);
		}
		pictureDao.save(picture);
		return picture;
	}

	private BufferedImage cropImage(BufferedImage sourceImage, int targetImageWidth, int targetImageHeight) {
		BufferedImage picAdjustedImage = null;
		if (sourceImage.getHeight() * (1.0f * targetImageWidth) / sourceImage.getWidth() > targetImageHeight) {
			picAdjustedImage = sourceImage.getSubimage(0, (sourceImage.getHeight() - sourceImage.getWidth() * targetImageHeight
					/ targetImageWidth) / 2, sourceImage.getWidth(), sourceImage.getWidth() * targetImageHeight / targetImageWidth);
		} else {
			picAdjustedImage = sourceImage.getSubimage((sourceImage.getWidth() - sourceImage.getHeight() * targetImageWidth
					/ targetImageHeight) / 2, 0, sourceImage.getHeight() * targetImageWidth / targetImageHeight, sourceImage.getHeight());
		}
		return picAdjustedImage;
	}

	@Override
	@Transactional
	public void delete(String entityType, long entityId, String photoFolder){
		if(ENTITY_TYPE_BUSINESS.equalsIgnoreCase(entityType)){
			Office office = officeService.findById(entityId);
			delete(office, photoFolder);
		}else if (ENTITY_TYPE_CUSTOMER.equalsIgnoreCase(entityType)){
			Customer customer = customerService.findById(entityId);
			delete(customer, photoFolder);
		}
	}

	private void delete(Customer customer, String photoFolder) {
		File dir = new File(docRoot + profileFolder + photoFolder);
		deleteDirectory(dir);
		for(Picture p : customer.getPictures()){
			if(p.getPictureFolder().equalsIgnoreCase(photoFolder)){
				if(customer.getPrimaryPicture() != null){
					if(customer.getPrimaryPicture().getId() == p.getId()){
						customer.setPrimaryPicture(null);
					}
				}
				customer.getPictures().remove(p);
				pictureDao.remove(p);
			}else{
				continue;
			}
		}
		customerService.update(customer);
	}

	private void delete(Office office, String photoFolder) {
		File dir = new File(docRoot +profileFolder + photoFolder);
		deleteDirectory(dir);
		for(Picture p : office.getPictures()){
			if(p.getPictureFolder().equalsIgnoreCase(photoFolder)){
				if(office.getPrimaryPicture() != null){
					if(office.getPrimaryPicture().getId() == p.getId()){
						office.setPrimaryPicture(null);
					}
				}
				office.getPictures().remove(p);
				pictureDao.remove(p);
				break;
			}else{
				continue;
			}
		}
		officeService.update(office);
	}
	
	private void deleteDirectory(File directory){
		if(directory.exists()){
			if(directory.isDirectory()){
				File[] entries = directory.listFiles();
	               for(File currentFile: entries){
	            	   deleteDirectory(currentFile);
	               }
			}else{
				directory.delete();
			}
			directory.delete();
		}
	}
}
